package model;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import GUI.MainFrame;



public class MonteCarlo {
	
	//Resources
	Result resultObject; //object containing : result of MC calculation, time performance
	double percentage; //compute progress (for progress bar)
	
	public MonteCarlo(){
		resultObject= new Result();
		percentage=0;
	}
	
	/**
	 * @param args
	 * @throws IOException 
	 * 
	 * 
	 */
	public ArrayList<SetOfArguments> load() throws IOException
	{
		
		final BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
		ArrayList<SetOfArguments> sets =new ArrayList<SetOfArguments>(); 
		
		String callPutFlag=null;
		double s=0.0;
		double x=0.0;
		double t=0.0;
		double r=0.0;
		double b=0.0;
		double v=0.0;
		int nSteps=0;
		int nSimulations=0;
		
		String stockInfo = null;
	    while((stockInfo = reader.readLine()) != null) 
	    {
	    	final String[] stockInfoData = stockInfo.split(",");
	    	callPutFlag=stockInfoData[0];
	    	s=Double.parseDouble(stockInfoData[1]);
	    	x=Double.parseDouble(stockInfoData[2]);
	    	t=Double.parseDouble(stockInfoData[3]);
	    	r=Double.parseDouble(stockInfoData[4]);
	    	b=Double.parseDouble(stockInfoData[5]);
	    	v=Double.parseDouble(stockInfoData[6]);
	    	
	    	nSteps=Integer.parseInt(stockInfoData[7]);
	    	nSimulations=Integer.parseInt(stockInfoData[8]);
	    	
	    	sets.add(new SetOfArguments(false,callPutFlag,s,x,t,r,b,v,nSteps,nSimulations));
	    }
	    
	    reader.close();		
	    
	    return sets;
	}
	
	/**
	 * Monte-Carlo calculation, sending all parameters
	 * @param CallPutFlag
	 * @param S
	 * @param X
	 * @param T
	 * @param r
	 * @param b
	 * @param v
	 * @param nSteps
	 * @param nSimulations
	 * @return the result of the calculation
	 * @throws InterruptedException 
	 * @throws ExecutionException 
	 */
	public Double monteCarloCalculation (String CallPutFlag, Double S,Double X, Double T, Double r, Double b, Double v, int nSteps, int nSimulations)
	{
		Double dt, drift, vSqrdt, St, expo;
		Double Sum=0.0;
		int z=0;
		Random random= new Random();
		Double result;
			
		dt= T/nSteps;
		drift=(b - Math.pow(v,2)/2)*dt; //Math.pow -> compute the power (2d argument) of the 1st argument	
		vSqrdt= v * Math.sqrt(dt); //Math.sqrt -> square root function
		
		if(CallPutFlag.equals("c")) z=1; //Call -> buy
		else if (CallPutFlag.equals("p")) z=-1; //Put -> sell
		
		for(int i=0;i<nSimulations;i++)
		{
			//System.out.println(""+i);
			
			St=S;
			for(int j=0;j<nSteps;j++)
			{
				expo=drift + vSqrdt*random.nextGaussian();
				St=St*Math.exp(expo);
			}
			
			Sum = Sum + Math.max (z * (St - X), 0);
			
			//set progress
			percentage = (i*100) / nSimulations;
			
			MainFrame.progress.setValue((int)percentage);
		}
		
		result=Math.exp (-r * T) * (Sum / nSimulations);
		
		//computing completed
		MainFrame.progress.setValue(100);
		
		return result;
	}
	
	public Double monteCarloCalculationMultiThread (String CallPutFlag, final double S,final double X, double T, double r, double b, double v, final int nSteps, int nSimulations) throws InterruptedException, ExecutionException
	{
		//Initialisation of variables
		double dt=T/nSteps;
		final double drift=(b-Math.pow(v,2)/2)*dt; //Math.pow -> compute the power (2d argument) of the 1st argument	
		final double vSqrdt=v*Math.sqrt(dt); //Math.sqrt -> square root function
		double Sum=0.0;
		final int z;
		
		//Determine the number of threads
		final int numberOfCores = Runtime.getRuntime().availableProcessors();
		resultObject.setCore(numberOfCores); //save value in the result object
		final double blockingCoefficient = 0.1;
		final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
		resultObject.setPoolSize(poolSize); //save value in the result object
		
		//Create a list that will memorize each calculation to sum them up later
		List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
		
		//Create an ExecutorService to manage the invokeAll() later
		ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
		
		//Call or Put
		if(CallPutFlag.equals("c")) z=1; //Call -> buy
		else if (CallPutFlag.equals("p")) z=-1; //Put -> sell
		else z=1;
		
		//For all simulations
		for(int i=0;i<nSimulations;i++)
		{
			
			partitions.add(new Callable<Double>() //Add the result to the partitions list
			{
		        public Double call() throws Exception 
		        {
		        	//Compute with Montecarlo the result
					double St=S; 
					Random random= new Random();
					for(int j=0;j<nSteps;j++)
					{
						St=St*Math.exp(drift + vSqrdt*random.nextGaussian());
					}
					
					return Math.max (z * (St - X), 0); //return value of the call() function
		        }
			});
			
			//set progress
			percentage = (i*100) / nSimulations;
			
			MainFrame.progress.setValue((int)percentage-9); //why -9 : 10% of time is for what happens after the calculation is completed and before the pop up appears
		}
		
		//Create a list of future that contain all the results of the simulations after they are all completed
		List<Future<Double>> valueOfSimulations = executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
		
		//set progress
		MainFrame.progress.setValue(MainFrame.progress.getValue() + 5);
		
		//We compute the sum after every calculation are finished (because they were done simultanously in different threads)
		for(Future<Double> valueOfASimulation : valueOfSimulations)  
			 Sum += valueOfASimulation.get();
		
		//set progress
		MainFrame.progress.setValue(MainFrame.progress.getValue() + 5);
		
		//Shutdown the ExecutorService
		executorPool.shutdown();
		
		//computing completed
		MainFrame.progress.setValue(100);
		
		//Return the result
		return Math.exp (-r * T) * (Sum / nSimulations);
	}
	
	/**
	 * Analyse Monte carlo calculation
	 * Call Monte-Carlo calculation, sending 1 set object
	 * Measure time to perform the calculation
	 * @param set : an object containing all parameters
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public void monteCarloCalculation (SetOfArguments set, String thread) throws InterruptedException, ExecutionException
	{
		//Resources
		Double result;
		//measure time
		long t_end;
		long t_start;
		long time;
		//get arguments
		String CallPutFlag=set.getCallPutFlag(); 
		Double S=set.getS();
		Double X=set.getX();
		Double T=set.getT();
		Double r=set.getR();
		Double b=set.getB();
		Double v=set.getV();
		int nSteps=set.getnSteps();
		int nSimulations=set.getnSimulations();
		
		//start
		t_start=System.currentTimeMillis();
		
		//calculation
		if(thread.equals("single"))
		{
			result= monteCarloCalculation(CallPutFlag,S,X,T,r,b,v,nSteps,nSimulations);
		}else
		{
			result= monteCarloCalculationMultiThread(CallPutFlag,S,X,T,r,b,v,nSteps,nSimulations);
		}
		
		//end
		t_end=System.currentTimeMillis();
		
		//compute, display time
		time=t_end-t_start;
		//System.out.println("Time : "+time+" milliseconds");
		//display result
		//System.out.printf("Result : %.4f",result);
		
		//update result object
		resultObject.setResult(result);
		resultObject.setTime(time);
	}

	
	//ACCESSORS
	
	public Result getResultObject() {
		return resultObject;
	}

	public void setResultObject(Result resultObject) {
		this.resultObject = resultObject;
	}
}
